package com.helloandroid.activesitting.bl;

import java.util.Calendar;
import java.util.Date;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;

import com.helloandroid.activesitting.ApplicationObject;
import com.helloandroid.activesitting.bc.PreferencesConnector;
import com.helloandroid.activesitting.bo.WorkTime;
import com.helloandroid.activesitting.util.Log;

public class TimeManager {
	private static TimeManager mInstance;
	private Date mLastSetUpAlarmTime;

	public static int THRESHOLD_BEFORE_LUNCHTIME = 30;
	public static int THRESHOLD_AFTER_LUNCHTIME = 60;
	public static int THRESHOLD_BEFORE_ENDTIME = 0;

	public static TimeManager GetInstance() {
		if (mInstance == null) {
			mInstance = new TimeManager();
		}
		return mInstance;
	}

	public void SetTimes(WorkTime workTimeStart, WorkTime workTimeEnd,
			WorkTime lunchBreakStart, WorkTime luncbreakEnd) {
		Log.m("TimeManager", "SetTimes");
		if (!PreferencesConnector.GetInstance().SetWorkTimeByKey(workTimeStart,
				PreferencesConnector.KEY_TIME_START_HOUR,
				PreferencesConnector.KEY_TIME_START_MIN)) {
			Log.d("SetTimes Error, PreferencesConnector SetWorkTimeByKey returned false");
		}

		if (!PreferencesConnector.GetInstance().SetWorkTimeByKey(workTimeEnd,
				PreferencesConnector.KEY_TIME_END_HOUR,
				PreferencesConnector.KEY_TIME_END_MIN)) {
			Log.d("SetTimes Error, PreferencesConnector SetWorkTimeByKey returned false");
		}

		boolean enabled= true;
		if (!PreferencesConnector.GetInstance().SetWorkTimeByKey(
				lunchBreakStart, PreferencesConnector.KEY_LUNCH_START_HOURS,
				PreferencesConnector.KEY_LUNCH_START_MINUTES)) {
			Log.d("SetTimes Error, PreferencesConnector SetWorkTimeByKey returned false");
			enabled= false;
		}

		if (!PreferencesConnector.GetInstance().SetWorkTimeByKey(luncbreakEnd,
				PreferencesConnector.KEY_LUNCH_END_HOURS,
				PreferencesConnector.KEY_LUNCH_END_MINUTES)) {
			Log.d("SetTimes Error, PreferencesConnector SetWorkTimeByKey returned false");
			enabled= false;
		}
		
		if (!PreferencesConnector.GetInstance().SetBooleanByKey(PreferencesConnector.KEY_LUNCH_ENABLED,enabled)) {
			Log.d("SetTimes Error, PreferencesConnector SetWorkTimeByKey returned false");
		}
	}
	
	public void SetTimes(WorkTime workTimeStart, WorkTime workTimeEnd) {
		Log.m("TimeManager", "SetTimes without lunchbreak");
		if (!PreferencesConnector.GetInstance().SetWorkTimeByKey(workTimeStart,
				PreferencesConnector.KEY_TIME_START_HOUR,
				PreferencesConnector.KEY_TIME_START_MIN)) {
			Log.d("SetTimes Error, PreferencesConnector SetWorkTimeByKey returned false");
		}

		if (!PreferencesConnector.GetInstance().SetWorkTimeByKey(workTimeEnd,
				PreferencesConnector.KEY_TIME_END_HOUR,
				PreferencesConnector.KEY_TIME_END_MIN)) {
			Log.d("SetTimes Error, PreferencesConnector SetWorkTimeByKey returned false");
		}
		
		if (!PreferencesConnector.GetInstance().SetBooleanByKey(PreferencesConnector.KEY_LUNCH_ENABLED,false)) {
			Log.d("SetTimes Error, PreferencesConnector SetBooleabByKey returned false");
		}

	}

	/**
	 * GetStartTime
	 * 
	 * @return
	 */
	public WorkTime GetStartTime() {
		Log.m("TimeManager", "GetStartTime");
		return PreferencesConnector.GetInstance().GetWorkTimeByKey(
				PreferencesConnector.KEY_TIME_START_HOUR,
				PreferencesConnector.KEY_TIME_START_MIN);
	}

	/**
	 * GetEndTime
	 * 
	 * @return
	 */
	public WorkTime GetEndTime() {
		Log.m("TimeManager", "GetEndTime");
		return PreferencesConnector.GetInstance().GetWorkTimeByKey(
				PreferencesConnector.KEY_TIME_END_HOUR,
				PreferencesConnector.KEY_TIME_END_MIN);
	}

	/**
	 * GetLunchMinutes
	 * 
	 * @return
	 */
	public WorkTime GetLunchBreakStart() {
		Log.m("TimeManager", "GetLunchBreakMinutes");
		return PreferencesConnector.GetInstance().GetWorkTimeByKey(
				PreferencesConnector.KEY_LUNCH_START_HOURS,
				PreferencesConnector.KEY_LUNCH_START_MINUTES);
	}

	public WorkTime GetLunchBreakEnd() {
		Log.m("TimeManager", "GetLunchBreakEnd");
		return PreferencesConnector.GetInstance().GetWorkTimeByKey(
				PreferencesConnector.KEY_LUNCH_END_HOURS,
				PreferencesConnector.KEY_LUNCH_END_MINUTES);
	}

	public boolean IsLunchBreakEnabled() {
		Log.m("TimeManager", "IsLunchBreakEnabled");
		return PreferencesConnector.GetInstance().GetBooleanByKey(PreferencesConnector.KEY_LUNCH_ENABLED);
		//return true;
	}
	
	/**
	 * Return false if there is something missing in the setup
	 * 
	 * @return
	 */
	public boolean IsWorktimeSetupDone() {
		Log.m("TimeManager", "IsWorktimeSetupDone");
		WorkTime start = GetStartTime();
		WorkTime end = GetEndTime();
		WorkTime lunch = GetLunchBreakStart();
		boolean enabled = IsLunchBreakEnabled();

		if (start.hour == 1000 || start.minute == 1000 || end.hour == 1000
				|| end.minute == 1000) {
			return false;
		} else if (enabled && (lunch.hour == 1000 || lunch.minute == 1000)){
			return false;
		}else {
			return true;
		}
	}

	/**
	 * getLastSetUpAlarmTime
	 */
	public Date getLastSetUpAlarmTime() {
		return mLastSetUpAlarmTime;
	}

	/**
	 * Start the pending intent
	 */
	public void StartTimers() {
		Log.m("TimeManager", "StartTimers");

		// if the worktime setup is not done, no alarm
		if (!IsWorktimeSetupDone()) {
			Log.d("the worktime setup is not done, The alarm has not been started");
			return;
		}

		AlarmManager mgr = (AlarmManager) ApplicationObject.ApplicationContext
				.getSystemService(Context.ALARM_SERVICE);
		Intent i = new Intent(ApplicationObject.ApplicationContext,
				AlarmReceiver.class);
		i.setAction(AlarmReceiver.ACTION_MOVE_TIME);

		PendingIntent pi = PendingIntent.getBroadcast(
				ApplicationObject.ApplicationContext, 0, i, 0);
		mgr.cancel(pi);

		mLastSetUpAlarmTime = getNextNotificationDate(new Date(
				System.currentTimeMillis()));
		long triggerAtTime = mLastSetUpAlarmTime.getTime();

		mgr.set(AlarmManager.RTC, triggerAtTime, pi);
		Log.d("AlarmAction is started : "+ mLastSetUpAlarmTime.toLocaleString());
		
		Intent im = new Intent(ApplicationObject.ApplicationContext,AlarmReceiver.class);
		im.setAction(AlarmReceiver.ACTION_MOTION_CHECK);
		PendingIntent pim = PendingIntent.getBroadcast(ApplicationObject.ApplicationContext, 1, im, 0);
		mgr.cancel(pim);
		
		Calendar c = Calendar.getInstance();
		c.setTime(mLastSetUpAlarmTime);
		c.add(Calendar.SECOND, -30);
		mgr.set(AlarmManager.RTC, c.getTimeInMillis(), pim);
	}

	/**
	 * Return next alarm time from now
	 * 
	 * @param 
	 *            time of request
	 * @return
	 */

	public Date getNextNotificationDate(Date now) {
		Log.m("TimeManager", "getNextNotificationDate");

		if (!isAWorkDay(now)) {
			return this.getNextNotificationDate(getNextWorkDay(now));
		}
		/* convert worktime to Date */
		Calendar calendar = Calendar.getInstance();
		Date workStartTime = null;
		Date workEndTime = null;
		Date lunchStartTime = null;
		Date lunchEndTime = null;
		// no alarm 'threshold minutes' before end of worktime
		Date workEndTimeWithThreshold = null;
		// no alarm 'threshold minutes' before start of lunchtime
		Date lunchStartTimeWithThreshold = null;
		// no alarm threshold minutes before start of lunchtime
		Date lunchEndTimeWithThreshold = null;

		Date alarmTime;
		calendar.setTime(now);
		alarmTime = calendar.getTime();

		WorkTime startWork = GetStartTime();
		WorkTime endWork = GetEndTime();
		WorkTime startLunch = GetLunchBreakStart();
		WorkTime endLunch = GetLunchBreakEnd();

		calendar.setTime(now);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);
		now = calendar.getTime();
		calendar.add(Calendar.DAY_OF_YEAR, 1);
		Date tomorrow = calendar.getTime();

		// setup work start and end date
		if (startWork.hour <= endWork.hour) {
			// start and end time are on the same day
			calendar.setTime(now);
			calendar.set(Calendar.HOUR_OF_DAY, startWork.hour);
			calendar.set(Calendar.MINUTE, startWork.minute);
			workStartTime = calendar.getTime();

			calendar.setTime(now);
			calendar.set(Calendar.HOUR_OF_DAY, endWork.hour);
			calendar.set(Calendar.MINUTE, endWork.minute);
			workEndTime = calendar.getTime();

		} else {
			// start and end time are on different day
			calendar.setTime(now);
			calendar.set(Calendar.HOUR_OF_DAY, startWork.hour);
			calendar.set(Calendar.MINUTE, startWork.minute);
			workStartTime = calendar.getTime();

			calendar.setTime(tomorrow);
			calendar.set(Calendar.HOUR_OF_DAY, endWork.hour);
			calendar.set(Calendar.MINUTE, endWork.minute);
			workEndTime = calendar.getTime();
		}

		if (IsLunchBreakEnabled()){
			// setup lunch start
			if (startWork.hour <= startLunch.hour) {
				// startwork time and lunch start time are on the same day
				calendar.setTime(now);
				calendar.set(Calendar.HOUR_OF_DAY, startLunch.hour);
				calendar.set(Calendar.MINUTE, startLunch.minute);
				lunchStartTime = calendar.getTime();
			} else {
				// start and end time are on different day
				calendar.setTime(tomorrow);
				calendar.set(Calendar.HOUR_OF_DAY, startLunch.hour);
				calendar.set(Calendar.MINUTE, startLunch.minute);
				lunchStartTime = calendar.getTime();
			}

			// setup lunch end
			if (startWork.hour <= endLunch.hour) {
				// start and end time are on the same day
				calendar.setTime(now);
				calendar.set(Calendar.HOUR_OF_DAY, endLunch.hour);
				calendar.set(Calendar.MINUTE, endLunch.minute);
				lunchEndTime = calendar.getTime();
			} else {
				// start and end time are on different day
				calendar.setTime(tomorrow);
				calendar.set(Calendar.HOUR_OF_DAY, endLunch.hour);
				calendar.set(Calendar.MINUTE, endLunch.minute);
				lunchEndTime = calendar.getTime();
			}
		}

		// Log.d("work start in Calendar, " + workStartTime.toLocaleString());
		// Log.d("work end in Calendar, " + workEndTime.toLocaleString());
		// Log.d("lunch start in Calendar, " + lunchEndTime.toLocaleString());
		// Log.d("lunch end in Calendar, " + lunchStartTime.toLocaleString());

		// no alarm 'threshold minutes' before end of worktime
		calendar.setTime(workEndTime);
		calendar.add(Calendar.MINUTE, -THRESHOLD_BEFORE_ENDTIME);
		workEndTimeWithThreshold = calendar.getTime();
		
		if(IsLunchBreakEnabled()){
			// no alarm 'threshold minutes' before start of lunchtime
			calendar.setTime(lunchStartTime);
			calendar.add(Calendar.MINUTE, -THRESHOLD_BEFORE_LUNCHTIME);
			lunchStartTimeWithThreshold = calendar.getTime();
	
			// no alarm 'threshold minutes' before end of lunchtime
			calendar.setTime(lunchEndTime);
			calendar.add(Calendar.MINUTE, THRESHOLD_AFTER_LUNCHTIME);
			lunchEndTimeWithThreshold = calendar.getTime();
			// calculating with dates
		}
		
		if (workStartTime.after(alarmTime)) {
			// ha alarm time is earlier than workstart
			calendar.setTime(workStartTime);
			alarmTime = calendar.getTime();
			// now in worktime
		} else if (workEndTime.before(alarmTime)) {
			calendar.setTime(getNextWorkDay(alarmTime));
			alarmTime = calendar.getTime();
			// now in next workday's worktime
			return this.getNextNotificationDate(alarmTime);
		}

		// in worktime thus to shift the alarmtime to the next hour
		if (!IsLunchBreakEnabled() || lunchStartTimeWithThreshold.after(alarmTime)) {
			alarmTime = getNextHourAfterWorkStart(alarmTime);
		} else {
			alarmTime = getNextHourAfterLunchEnd(alarmTime);
		}

		if (IsLunchBreakEnabled() && lunchStartTimeWithThreshold.before(alarmTime)
				&& lunchEndTimeWithThreshold.after(alarmTime)) {
			// in lunchtime (red zone, need to shift out of here )
			alarmTime.setTime(getNextHourAfterLunchEnd(lunchEndTime).getTime());
		}

		if (workEndTimeWithThreshold.before(alarmTime)) {
			calendar.setTime(getNextWorkDay(alarmTime));
			alarmTime = calendar.getTime();
			return this.getNextNotificationDate(alarmTime);
		}

		return alarmTime;
	}

	/**
	 * Return false if the received Date is not workday (By default we don't
	 * work at weekends)
	 * 
	 * @return
	 */
	private boolean isAWorkDay(Date now) {
		Log.m("TimeManager", "isAWorkDay");
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(now);

		switch (calendar.get(Calendar.DAY_OF_WEEK)) {
		case Calendar.SATURDAY:
			return false;
		case Calendar.SUNDAY:
			return false;
		default:
			return true;
		}

	}

	/**
	 * Return Date of next workday (By default we don't work at weekends)
	 * 
	 * @return
	 */
	private Date getNextWorkDay(Date now) {
		Log.m("TimeManager", "getNextWorkDay");
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(now);

		switch (calendar.get(Calendar.DAY_OF_WEEK)) {
		case Calendar.FRIDAY:
			calendar.add(Calendar.DAY_OF_YEAR, 3);
			break;
		case Calendar.SATURDAY:
			calendar.add(Calendar.DAY_OF_YEAR, 2);
			break;
		default:
			calendar.add(Calendar.DAY_OF_YEAR, 1);
			break;
		}

		WorkTime startWork = GetStartTime();
		calendar.set(Calendar.HOUR_OF_DAY, startWork.hour);
		calendar.set(Calendar.MINUTE, startWork.minute);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);
		// TODO: to implement GoogleCalendar
		return calendar.getTime();
	}

	private Date getNextHourAfterWorkStart(Date now) {
		Log.m("TimeManager", "getNextHourAfterWorkStart");
		// TODO: GetStartTime() is volatile ,to solve this problem
		WorkTime startWork = GetStartTime();
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(now);
		if (calendar.get(Calendar.MINUTE) < startWork.minute) {
			calendar.set(Calendar.MINUTE, startWork.minute);
		} else {
			calendar.set(Calendar.MINUTE, startWork.minute);
			calendar.add(Calendar.HOUR_OF_DAY, 1);
		}
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);
		return calendar.getTime();
	}

	private Date getNextHourAfterLunchEnd(Date now) {
		Log.m("TimeManager", "getNextHourAfterLunchEnd");
		// TODO: GetStartTime() is volatile ,to solve this problem
		WorkTime startWork = GetLunchBreakEnd();
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(now);
		if (calendar.get(Calendar.MINUTE) < startWork.minute) {
			calendar.set(Calendar.MINUTE, startWork.minute);
		} else {
			calendar.set(Calendar.MINUTE, startWork.minute);
			calendar.add(Calendar.HOUR_OF_DAY, 1);
		}
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);
		return calendar.getTime();
	}

	/**
	 * Cancel a pending intent
	 */
	public void StopTimers() {
		Log.m("TimeManager", "StopTimers");
		Intent i = new Intent(ApplicationObject.ApplicationContext,
				AlarmReceiver.class);
		PendingIntent pi = PendingIntent.getBroadcast(
				ApplicationObject.ApplicationContext, 0, i, 0);
		pi.cancel();
		
		Intent im = new Intent(ApplicationObject.ApplicationContext,
				AlarmReceiver.class);
		PendingIntent pim = PendingIntent.getBroadcast(
				ApplicationObject.ApplicationContext, 1, im, 0);
		pim.cancel();
		mLastSetUpAlarmTime = null;
	}

	public WorkTime GetNextNotificationTime() {

		return new WorkTime(12, 30);
	}
}
